﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Windows.Forms;

namespace MMOShared
{
    [Serializable]
    public enum CompileOptions {
        NEEDS_COMPILE,
        NO_COMPILE,
        DONE_COMPILE
    };

    [Serializable]
    public class ResourceEntry
    {
        public string RelativePath {get;set;}
        public string ContentPath {get;set;}
        public string ResourceID {get;set;}
        public string CheckSum {get;set;}
        public CompileOptions CompileOptions { get; set; }

        public ResourceEntry(string relPath,string contPath,string resID,string checkSum,CompileOptions compile){
            RelativePath = relPath;
            ContentPath = contPath;
            ResourceID = resID;
            CheckSum = checkSum;
            CompileOptions = compile;
        }

        public void CalculateSHA1()
        {
            using (HashAlgorithm hashAlg = new SHA1Managed())
            {
                using (Stream file = new FileStream(RelativePath, FileMode.Open, FileAccess.Read))
                {
                    byte[] hash = hashAlg.ComputeHash(file);

                    CheckSum = BitConverter.ToString(hash);
                }
            }
        }
        public bool VerifyFile(){
            using (HashAlgorithm hashAlg = new SHA1Managed())
            {
                using (Stream file = new FileStream(RelativePath, FileMode.Open, FileAccess.Read))
                {
                    byte[] hash = hashAlg.ComputeHash(file);

                    //compare hashes
                    if (!String.Equals(CheckSum, BitConverter.ToString(hash))){
                        MessageBox.Show("File checksum failed! In file \""+RelativePath+"\"");
                        return false;
                    }
                }
            }
            return true;
        }
    }
    [Serializable]
    public class ResourceManager
    {
        public UInt16 Version { get; private set; }
        public List<ResourceEntry> Resources { get; private set; }
        public Dictionary<string, string> Lookup { get; private set; }
        public bool IsLoaded { get; private set; }
        public bool NeedsCompile { get; private set; }

        public static ResourceManager Load(string resourcePath){
            Stream stream = null;
            ResourceManager ret = null;
            try {
                IFormatter formatter = new BinaryFormatter();
                stream = new FileStream(resourcePath, FileMode.Open, FileAccess.Read, FileShare.None);
                ret = (ResourceManager)formatter.Deserialize(stream);
            } catch (Exception e) {
                Console.WriteLine("Error loading ResourceManager: "+resourcePath + "\n"+e.Message);
                return null;
            } finally {
                if (null != stream)
                    stream.Close();
            }
            ret.IsLoaded = true;
            ret.NeedsCompile = ret.NeedsCompilation();
            return ret;
        }

        private bool NeedsCompilation()
        {
            foreach (ResourceEntry res in Resources)
            {
                if (res.CompileOptions == CompileOptions.NEEDS_COMPILE)
                    return true;
            }
            return false;
        }

        private bool FileNeedsUpdate(ResourceEntry newFile)
        {
            for (int i = 0; i < Resources.Count; i++)
            {
                ResourceEntry oldFile = Resources[i];
                if (String.Equals(newFile.ResourceID, oldFile.ResourceID))
                {
                    if (!String.Equals(newFile.CheckSum, oldFile.CheckSum))
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            //if we got here the new file wasn't found in the resources collection, so we need it
            return true;
        }

        public void AddResource(ResourceEntry r)
        {
            this.Resources.Add(r);
            this.Lookup.Add(r.ResourceID, r.RelativePath);
            if (r.CompileOptions == CompileOptions.NEEDS_COMPILE)
                this.NeedsCompile = true;
        }

        public void UpdateResource(int index, ResourceEntry updatedRes)
        {
            //remove old references
            ResourceEntry oldRes = Resources[index];
            Lookup.Remove(oldRes.ResourceID);
            //set new references
            this.Resources[index] = updatedRes;
            this.Lookup.Add(updatedRes.ResourceID, updatedRes.RelativePath);
            if (updatedRes.CompileOptions == CompileOptions.NEEDS_COMPILE)
                this.NeedsCompile = true;
        }

        private void BuildDictionary()
        {
            this.Lookup = new Dictionary<string, string>();
            foreach (ResourceEntry e in this.Resources)
            {
                this.Lookup.Add(e.ResourceID, e.RelativePath);
            }
        }

        public bool Save(string path)
        {
            Stream stream = null;
            bool status = true;
            try
            {
                IFormatter formatter = new BinaryFormatter();
                stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
                formatter.Serialize(stream, this);
            }
            catch(Exception e)
            {
                MessageBox.Show("Error saving ResourceManager: " + path + ", " + e.Message);
                status = false;
            }
            finally
            {
                if (null != stream)
                    stream.Close();
            }
            return status;
        }

        public ResourceManager(ushort version)
        {
            Version = version;
            Resources = new List<ResourceEntry>();
            Lookup = new Dictionary<string, string>();
            IsLoaded = false;
            NeedsCompile = false;
        }

        public void CalculateHashes()
        {
            for (int i = 0; i < Resources.Count; i++)
            {
                Resources[i].CalculateSHA1();
            }
        }

        public bool VerifyResources()
        {
            for (int i = 0; i < Resources.Count; i++)
            {
                if (!Resources[i].VerifyFile())
                {
                    return false;
                }
            }
            //all clean
            return true;
        }

        public void CreateCompilationList(out List<int> compileIndecies)
        {
            compileIndecies = new List<int>();
            if (!this.NeedsCompile) return;
            for(int i = 0; i < Resources.Count;i++){
                if(Resources[i].CompileOptions == CompileOptions.NEEDS_COMPILE){
                    compileIndecies.Add(i);
                }
            }
        }

        public bool CheckForUpdates(ResourceManager newVersion, out List<int> updateIndecies)
        {
            List<int> newIndecies = new List<int>();
            if (this.Version >= newVersion.Version)
            {
                //no need to update
                updateIndecies = null;
                return false;
            }
            else
            {
                for (int i = 0; i < newVersion.Resources.Count; i++)
                {
                    if (FileNeedsUpdate(newVersion.Resources[i]))
                    {
                        newIndecies.Add(i);
                    }
                }
                updateIndecies = newIndecies;
                return true;
            }
        }       
    }
}
